// hpp 后缀结尾的是表示 h  和  pp     头源不分离

// 头文件对于hpp 具有两种写法:
// 写法一：
//  #pragma once

// 写法二：
#ifndef __PROCESS_POOL_HPP__
#define __PROCESS_POOL_HOO__        //条件编译（预处理期间做的）
// hpp中在这中间写函数 可以实现头源不分离
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib> //exit的头文件
#include "Task.hpp"
#include <sys/wait.h>    //waitpid 的头文件


// 通道    把被管理对象描述起来
class Channel
{
public:
    // 构造信道
    // 构建一个channel 对象
    Channel(int fd, pid_t id) : _wfd(fd), _subid(id)
    {
        _name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);
        // to_string  表示把一个整数转为字符串
    }

    // 拿取内部成员变量的值
    int GetFd() { return _wfd; }
    pid_t GetSubid() { return _subid; }
    std::string GetName() { return _name; }

    // 析构
    ~Channel() {}

    //提供一个关闭文件描述符的接口
    void CloseFd()
    {
        close(_wfd);
    }

    //提供进程等待
    void Wait()
    {
        pid_t rid = waitpid(_subid,nullptr,0);
        (void) rid; 
    }

    // 发送任务
    void Send(int code)
    {
        int n = write(_wfd, &code, sizeof(code));
        (void)n; // 这个是防止编译器对未使用的变量做语法检查
    }

private:
    int _wfd;          // 只考虑父进程视角，父进程视角一定要有一个int，int表示是文件描述符  只需往_wfd中写
    pid_t _subid;      // 这个是当前channel对应的子进程是谁  信道是给哪个子进程的
    std::string _name; // 信道的名字是谁
};


// 再组织   用对应的数据结构组织起来
class ChannelManager
{
public:
    // 构造
    ChannelManager() : _next(0)
    {
    } // 初始化为0

    void PrintChannel()
    {
        for (auto &channel : _channels)
        {
            std::cout << channel.GetName() << std::endl;
        }
    }

    // 这个是搜索  看传递哪一个 Channel
    Channel &Select()
    {
        auto &c = _channels[_next];
        _next++;
        _next %= _channels.size();
        return c;
    }

    // 提供一个Insert管道接口
    void InsertChannel(int wfd, pid_t subid)
    {
        // 进行插入操作 有多个方式
        _channels.emplace_back(wfd, subid);

        // 下面两行和上面一行的操作是一样的
        //  Channel c(wfd,subid);     //构建出来channel对象
        //  _channels.push_back(c);   //将构建出来的channel 对象  插入vector中
    }


    //这个是把进程池退出的接口   具体操作方式：关闭子进程的写端（关闭管道写端  子进程read返回值就变成了0 ）
    //变成了0   变为了0子进程也就退出了
    void StopSubProcess()
    {
        //只需要关闭父进程所有的  wfd  即可(被ChannelManager所管理)  
        for(auto &channel : _channels)     //遍历所有的channel
        {
            //对于这里的关闭 ---> 实际上是channel 内部的关闭 （关闭文件描述符）
            channel.CloseFd();    //也就把所有的子进程全部关闭，父进程就会读到0   就会自动退出
            std::cout<<"关闭子进程："<<channel.GetName()<<std::endl;
        }
    }
    
    //回收所有的子进程(也被ChannelMannager所管理)
    //等待进程（子进程全部退出就要等待）
    void WaitSubProcess()
    {
        for(auto &channel : _channels)
        {
            channel.Wait();
            std::cout<<"回收子进程："<<channel.GetName()<<std::endl;
        }
    }


    // 析构
    ~ChannelManager() {}

private:
    std::vector<Channel> _channels; // 父进程对信道做管理实际就是对这个vector进行管理    先描述再组织
    int _next;
};

const int gdefaultnum = 5;

// 上面包上头文件  这里直接写函数  进行实现     在cc文件中包了头文件 可以直接调用    实验写的方法
//  void fun()
//  {
//      std::cout<<"hello hpp"<<std::endl;
//  }


// 进程池
class ProcessPool
{
public:
    // 构造
    ProcessPool(int num) : _process_num(num)    //这里仅是传递想要几个进程
    {
        // 子进程 给任务
        _tm.Register(PrintLog); // 这是注册任务  给上子进程的任务打印日志
        _tm.Register(DownLoad); // 下载任务
        _tm.Register(UpLoad);   // 上传任务
    } // 这里的构造函数需要有提供一个num  也就是个数  需要告诉创建进程池的时候需要几个

    void Work(int rfd)
    {
        while (true)
        {
            // std::cout << "我是子进程！我的rfd是：" << rfd << std::endl;
            // sleep(5);

            // 任务
            int code = 0;
            ssize_t n = read(rfd, &code, sizeof(code)); // 从rfd进行read  一次写四个字节  从管道里
            if (n > 0)   // 读到了code     读取任务码
            {
                if (n != sizeof(code)) // 必须要严格遵守读取 4字节
                {
                    continue; // 不为4   就继续
                }

                // 到这里一定是读取规范了    读取任务码之后执行对应的任务
                std::cout << "子进程[" << getpid() << "]" << "收到了一个任务码：" << code << std::endl;
                _tm.Execute(code);     //执行对应的任务
            }

            else if (n == 0)
            {
                std::cout << "子进程  退出！" << std::endl;
                break;
            }

            else
            {
                // 读失败
                std::cout << "抱歉  读取错误！" << std::endl;
                break;
            }
        }
    }

    // 创建一个进程池
    bool Create()
    {
        // 执行for循环的代码只有父进程能跑  子进程一旦创建就会去走work，跑完就会关闭并退出
        for (int i = 0; i < _process_num; i++)
        {
            // --------------------   这些是一个信道   --------------------
            // 先创建一条信道
            // 1.创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                return false; // 管道创建失败
            }

            // 2.创建子进程
            pid_t subid = fork();
            if (subid < 0)
                return false; // 子进程创建失败
            else if (subid == 0)
            {
                // 要求 父进程写   子进程读
                // 3. 关闭 不需要的文件描述符
                close(pipefd[1]); // 子进程用来读  所以关闭1（写）

                // 子进程进行工作(子进程做什么工作是从管道读进来的,所以就传pipefd[0])
                Work(pipefd[0]);    //子进程关闭不需要的文件描述符就可以执行自己的工作代码（读  不写就会在读的地方阻塞住）

                // 做完上面的工作就关掉读端就行
                close(pipefd[0]);

                // child 子进程
                exit(0);
            }
// -------------------------  注意： 父子都要关闭不需要的文件描述符   ---------------------------------
            else
            {
                // father 父进程
                close(pipefd[0]); // 父进程用来写   所以关闭0（读）
                // 父进程写端的管道 pipefd[1];
                // 到这里  父进程每创建一个管道  就要给ChannelManger

// -----------------------   父进程要动态选择性的往指定的信道中push对应的任务  ----------------------------------
                _cm.InsertChannel(pipefd[1], subid);      //插入对应的新的写端  和  子进程的pid
                // 创建结束子进程  子进程写端 & 子进程id知道
                // 构建一个通信信道  第一个参数传的就是pipefd[1]

                //   wfd   subid
            }

            // ----------------  这是一个信道  -------------------------
        }
        return true;
    }

    // Debug   测试输出
    void Debug()
    {
        _cm.PrintChannel();
    }


    // 进程池  跑起来
    void Run()
    {
            //步骤一：选择一个任务
            int taskcode = _tm.Code();      //taskcode 选择一个任务码

            //派发任务  （用轮询）
            //步骤二：选择一个信道[子进程]，负载均衡的进行选择一个进程！完成任务！
            auto &c = _cm.Select();
            std::cout<<"这里选择一个子进程："<<c.GetName()<<std::endl;

            //步骤三：发送任务
            c.Send(taskcode);    //发送任务码   给子进程
            std::cout<<"这里发送一个任务码："<<taskcode<<std::endl;
    }
    // //信道任务
    // void PushTask(int taskcode)    //要传任务码
    // {
    //     //派发任务  （用轮询）
    //     //步骤一：选择一个信道，负载均衡的进行选择一个进程！完成任务！
    //     auto &c = _cm.Select();
    //     std::cout<<"这里选择一个子进程："<<c.GetName()<<std::endl;

    //     //步骤二：发送任务
    //     c.Send(taskcode);
    //     std::cout<<"这里发送一个任务码："<<taskcode<<std::endl;
    // }


    //进程池的退出
    //关闭父进程所有的  wfd  
    void StopProcessPool()
    {
        //关闭父进程的所有wfd
        _cm.StopSubProcess();

        //对所有的子进程进行回收
        _cm.WaitSubProcess();

    }

    // 析构
    ~ProcessPool() {}

private:
    // 进程池中一定有 ChannelManager
    ChannelManager _cm; // 这是一个进程池    信道的管理模块
    int _process_num;   // 这个是创建进程池  进程的个数（这样管道的个数就确定了）
    TaskManager _tm;    // 这是任务的管理模块
};

#endif